package com.magenta.maxoptra.gis.distancematrix;

import com.magenta.maxoptra.gis.GeographicCoordinates;

import java.util.*;

/**
 * @author Sergey Grachev
 */
final class DistanceMatrixDAOUtils {

    private DistanceMatrixDAOUtils() {
    }

    public static MergeMatrixResult merge(final List<MatrixCoordinates> newMatrices, final Map<Long, Set<GeographicCoordinates>> existsMatrices) {
        final MergeMatrixResult result = new MergeMatrixResult();

        // remove group
        if (newMatrices.isEmpty()) {
            result.setRemoveGroup(true);
            return result;
        }

        final Map<Long, Set<GeographicCoordinates>> removeMatrices = new HashMap<Long, Set<GeographicCoordinates>>(existsMatrices);
        for (final MatrixCoordinates matrixCoordinates : newMatrices) {
            final long matrixId = matrixCoordinates.getMatrixId();

            // update exists matrix
            if (existsMatrices.containsKey(matrixId)) {

                // overall matrix will be removed
                if (matrixCoordinates.getCoordinates().isEmpty()) {
                    continue;
                }

                removeMatrices.remove(matrixId);

                final Set<GeographicCoordinates> existsCoordinates
                        = new HashSet<GeographicCoordinates>(existsMatrices.get(matrixCoordinates.getMatrixId()));

                // add new
                boolean noExistsCoordinates = true;
                for (final GeographicCoordinates coordinate : matrixCoordinates.getCoordinates()) {
                    if (existsCoordinates.contains(coordinate)) {
                        existsCoordinates.remove(coordinate);
                        noExistsCoordinates = false;
                    } else {
                        result.addNewCoordinate(matrixId, coordinate);
                    }
                }

                if (noExistsCoordinates) {
                    result.addRemoveMatrices(matrixId);
                } else {
                    // remove rest coordinates
                    result.addRemoveCoordinates(matrixId, existsCoordinates);
                }

            } else {

                // add all
                result.addNewCoordinate(matrixId, matrixCoordinates.getCoordinates());

            }
        }

        result.addRemoveMatrices(removeMatrices.keySet());

        return result;
    }

    public static final class MergeMatrixResult {
        private boolean removeGroup;
        private Set<Long> removedMatrices;
        private Map<Long, Set<GeographicCoordinates>> removedCoordinates;
        private Map<Long, Set<GeographicCoordinates>> addedCoordinates;

        public boolean isNoChanges() {
            return !removeGroup && removedMatrices == null && removedCoordinates == null && addedCoordinates == null;
        }

        public boolean isRemoveGroup() {
            return removeGroup;
        }

        public Set<Long> getRemovedMatrices() {
            return removedMatrices;
        }

        public Map<Long, Set<GeographicCoordinates>> getRemovedCoordinates() {
            return removedCoordinates;
        }

        public Map<Long, Set<GeographicCoordinates>> getAddedCoordinates() {
            return addedCoordinates;
        }

        public void setRemoveGroup(final boolean removeGroup) {
            this.removeGroup = removeGroup;
        }

        public boolean hasRemovedCoordinates() {
            return removedCoordinates != null && !removedCoordinates.isEmpty();
        }

        public boolean hasRemovedMatrices() {
            return removedMatrices != null && !removedMatrices.isEmpty();
        }

        public boolean hasAddedCoordinates() {
            return addedCoordinates != null && !addedCoordinates.isEmpty();
        }

        private Set<GeographicCoordinates> ensureMatrixExists(final long matrixId) {
            if (addedCoordinates == null) {
                addedCoordinates = new HashMap<Long, Set<GeographicCoordinates>>();
            }

            Set<GeographicCoordinates> result = addedCoordinates.get(matrixId);
            if (result == null) {
                addedCoordinates.put(matrixId, result = new HashSet<GeographicCoordinates>());
            }

            return result;
        }

        private Set<Long> ensureRemovedMatricesExists() {
            if (removedMatrices == null) {
                removedMatrices = new HashSet<Long>();
            }
            return removedMatrices;
        }

        public void addNewCoordinate(final long matrixId, final GeographicCoordinates coordinate) {
            ensureMatrixExists(matrixId).add(coordinate);
        }

        public void addNewCoordinate(final long matrixId, final List<GeographicCoordinates> coordinates) {
            if (coordinates.isEmpty()) {
                return;
            }

            ensureMatrixExists(matrixId).addAll(coordinates);
        }

        public void addRemoveCoordinates(final long matrixId, final Set<GeographicCoordinates> coordinates) {
            if (coordinates.isEmpty()) {
                return;
            }

            if (removedCoordinates == null) {
                removedCoordinates = new HashMap<Long, Set<GeographicCoordinates>>();
            }

            Set<GeographicCoordinates> matrixList = removedCoordinates.get(matrixId);
            if (matrixList == null) {
                removedCoordinates.put(matrixId, matrixList = new HashSet<GeographicCoordinates>());
            }

            matrixList.addAll(coordinates);
        }

        public void addRemoveMatrices(final long matrixId) {
            ensureRemovedMatricesExists().add(matrixId);

        }

        public void addRemoveMatrices(final Set<Long> matrices) {
            if (matrices.isEmpty()) {
                return;
            }

            ensureRemovedMatricesExists().addAll(matrices);
        }

        @Override
        public String toString() {
            return "MergeMatrixResult{" +
                    "removeGroup=" + removeGroup +
                    ", removedMatrices=" + removedMatrices +
                    ", removedCoordinates=" + removedCoordinates +
                    ", addedCoordinates=" + addedCoordinates +
                    '}';
        }
    }
}
